import os
from typing import Any
import asyncio
import platform
import circle_chain
from circle_chain.common import build_url_template, http_json_get, http_json_post


def fetch_my_block(address: str, timeout: int):
    """
    Fetch the block associated with the given address.
    The return data:
    { 'status': 200, 'data': block_data, 'message': 'success' }
    :param address: the address to fetch the block for
    :param timeout: the timeout for the API request
    :return: block data
    """
    url = build_url_template('wallet', 'fetchMyBlock')
    return http_json_get(url, { "address": address }, timeout)


def post_my_block(address: str, header_hex: str, channel_id: str, timeout: int):
    """
    Post the block to the network.
    The return data:
    { 'status': 200, 'data': response_data, 'message': 'success' }
    :param address: the address to post the block from
    :param header_hex: the block header in hexadecimal format
    :param channel_id: the channel id to use for the connection
    :param timeout: the timeout for the API request
    :return: response data
    """
    url = build_url_template('wallet', 'postMyBlock')
    return http_json_post(url, {
        "address": address,
        "blockHeaderHex": header_hex,
        "channelId": channel_id
    }, timeout)

def get_start_end(i: int, thread_count: int):
    """
    Calculate the start and end range for a given thread index.
    
    :param i: The index of the current thread
    :param thread_count: The total number of threads
    :return: A tuple containing the start and end range as integers
    """
    span = 100 // thread_count
    if i + 1 == thread_count:
        return (i * span, 100)
    return (i * span, (i + 1) * span)

def get_ccm_path():
    """
    Get the path to the ccm tool based on the current operating system and architecture.
    
    :return: The path to the ccm tool
    """
    os_name = platform.system().lower()
    if os_name == 'windows':
        os_name = 'windows'
    elif os_name == 'darwin':
        os_name = 'mac'
    elif os_name == 'linux':
        os_name = 'linux'
    
    arch = platform.machine().lower()
    fname = 'ccm'
    if os_name == 'windows':
        fname += '.exe'
    
    # Return relative path for testing, but use full path in actual execution
    relative_path = f'tools/{os_name}/{arch}/{fname}'
    
    # Get the full path for actual execution
    base_dir = os.path.dirname(circle_chain.__file__)
    full_path = f'{base_dir}/{relative_path}'
    
    if not os.path.exists(full_path):
        raise FileNotFoundError(f'{full_path} not found')
    
    return full_path

async def run_ccm_process(ccm_path, start, end, header_hex):
    """
    Run the ccm mining process with the specified parameters.
    
    :param ccm_path: Path to the ccm executable
    :param start: Start range for mining
    :param end: End range for mining
    :param header_hex: The block header in hexadecimal format
    :return: Tuple of (returncode, stdout, stderr)
    """
    try:
        # Make sure the path is executable
        if not os.access(ccm_path, os.X_OK):
            os.chmod(ccm_path, 0o755)
            
        print(f"Starting CCM process with range {start}-{end}")
        process = await asyncio.create_subprocess_exec(
            ccm_path, f'{start}-{end}', header_hex,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE
        )
        print(f"Process started with PID: {process.pid}")
        
        stdout, stderr = await process.communicate()
        stdout_str = stdout.decode('utf-8') if stdout else ""
        stderr_str = stderr.decode('utf-8') if stderr else ""
        
        print(f'Process completed with return code: {process.returncode}')
        print(f'Process completed stdout: {stdout_str}')
        print(f'Process completed stderr: {stderr_str}')
        
        return process.returncode, stdout_str, stderr_str
    except Exception as e:
        print(f"Error running CCM process: {str(e)}")
        return -1, "", str(e)
    
async def mine_with_threads(ccm_path, ranges, header_hex):
    """
    Mine a block using multiple threads.
    
    :param ccm_path: Path to the ccm executable
    :param ranges: List of (start, end) ranges for each thread
    :param header_hex: The block header in hexadecimal format
    """
    tasks = []
    
    # Create and store both the tasks and their associated processes
    for start, end in ranges:
        task = asyncio.create_task(run_ccm_process(ccm_path, start, end, header_hex))
        tasks.append(task)
        # Store the task so we can get its result later
        
    try:
        # Wait for the first task to complete
        done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
        
        # Cancel remaining tasks
        for task in pending:
            task.cancel()
            
        # Check results from completed task
        for task in done:
            await task
            
    finally:
        # Find and terminate any remaining ccm processes
        if platform.system() == 'Windows':
            os.system(f'taskkill /F /IM {os.path.basename(ccm_path)} 2>nul')
        else:
            os.system(f'pkill -f {os.path.basename(ccm_path)} 2>/dev/null')
        
        print("All mining processes terminated")

def read_mined_result():
    """
    Read the mining result from the output file.
    
    :return: The mined block header
    """
    with open('mined.txt', 'r') as file:
        return file.readline().strip()

def mine_block(header_hex: str, thread_count: int):
    """
    Mines a block using the provided block header and number of threads.

    This function calculates the range for each thread and uses the ccm tool
    to perform the mining operation. It reads the result from a file once
    mining is completed.

    :param header_hex: The block header in hexadecimal format.
    :param thread_count: The number of threads to use for mining.
    :return: The mined block header.
    """
    # Calculate the ranges for each thread
    ranges = [get_start_end(i, thread_count) for i in range(thread_count)]
    
    # Get the path to the ccm tool
    ccm_path = get_ccm_path()
    
    # Run the mining operation
    # The mine_with_threads function already handles waiting for tasks
    # It uses asyncio.wait() which waits for the first task to complete
    # and then cancels the remaining tasks
    asyncio.run(mine_with_threads(ccm_path, ranges, header_hex))
    
    # Read and return the result
    return read_mined_result()